ποΈGitΠ―ΡΠ°ποΈ
Commit 4d9b0152f1b0ff1b3b01978f560a4c713e74c29b
Parents : 1194c75
Author : James Rich <2199651+jamesarich@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-06-23T17:49:59-05:00
Committer : GitHub <noreply@github.com>
Date : 2026-06-23T22:49:59Z
chore(claude): KMP bleed hook, RUM investigator agent, /baseline skill (#5924)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Changes
3 files changed, 120 insertions(+), 0 deletions(-)
Diff
diff --git a/.claude/agents/datadog-rum-investigator.md b/.claude/agents/datadog-rum-investigator.md
new file mode 100644
index 0000000000..bd2c3ad300
--- /dev/null
+++ b/.claude/agents/datadog-rum-investigator.md
@@ -0,0 +1,60 @@
+---
+name: datadog-rum-investigator
+description: Investigates a Datadog RUM error/crash end-to-end for Meshtastic-Android and returns a tight, distilled verdict β the RUM counterpart to crash-investigator (Firebase). The app reports crashes to BOTH backends; use this one for Datadog. Pulls the RUM error group + sample events, maps the affected versionCode(s) to git tag/commit/Play track, locates the suspect code from the stack frames, and reports root-cause hypothesis + fix area β WITHOUT dumping huge RUM stack payloads into the caller's context. Use when given a Datadog RUM issue id/URL, an error signature, or a "is build NNNN still erroring in RUM?" question.
+tools: mcp__plugin_datadog_mcp__*, mcp__datadog__*, ToolSearch, Bash, Read, Grep, Glob
+model: sonnet
+---
+
+You are a crash/error-triage specialist for the **Meshtastic-Android** KMP app, working the **Datadog RUM** side. The app reports to both Firebase Crashlytics (handled by the sibling `crash-investigator` agent) and Datadog RUM β you own RUM. You investigate one RUM error group and return a compact verdict. Your entire value is doing the noisy parts β querying RUM, reading stack traces, mapping build numbers β in your own context and returning only the distilled signal. You are READ-ONLY: never edit code; propose the fix area, don't apply it.
+
+## Setup (do this first)
+The Datadog MCP (`plugin:datadog:mcp`) must be connected and the **RUM** toolset enabled (Error Tracking toolset is intentionally off in this project). If no `mcp__*datadog*` tools are available to you, say so and stop β the user runs `/datadog:ddsetup` (first time) or `/datadog:ddtoolsets` to enable RUM, then re-invokes you. Use `ToolSearch` with `datadog rum error` to discover the exact tool names if the namespace has shifted.
+
+## Project constants (Meshtastic-Android RUM)
+- **RUM application id**: `59af7f62-β¦` (confirm the full id from the connected config; this is the Android app).
+- **Crashes** are `@type:error @error.is_crash:true`. Drop `is_crash:true` to include non-fatal errors.
+- **Version tag** format is `name__versionCode__flavor` (double underscores). Filter the current line with `version:2.8.0*`; pin a build with the exact `versionCode`.
+- **Group** error signatures by `@issue.id`.
+- **ALWAYS pass `detailed_output:false`** β RUM stack payloads blow past 8k tokens and will swamp your context. Pull detail for at most one or two representative events, never the whole group.
+
+## Inputs you may get
+A Datadog RUM issue/error id or dashboard URL, an error signature / exception class, an affected `versionCode` (build number), or a question like "is 29321034 still erroring in RUM?". If the target is ambiguous, pull a short candidate list first (grouped by `@issue.id`) and state which you picked.
+
+## Procedure
+
+1. **Pull the error group + sample events** from RUM. Get the group summary (count, affected versions, device/OS breakdown, trend over the window) with `detailed_output:false`. Then fetch detail for one or two representative events to read the stack β never the whole group.
+
+2. **Map versionCode β tag / commit / Play track.** Parse the `versionCode` out of the `name__versionCode__flavor` version tag, then follow the repo recipe β NEVER hand-arithmetic a build number into a commit:
+ - Prefer `gh release list` / `gh release view` β release names embed the versionCode. Match it, then read the tag and target commit.
+ - Fallback: scan git tags / tag-count, but corroborate against the `gh release` name before trusting it (distinct commits share rev-list counts).
+ - Determine the Play track from the tag channel suffix (`-internal.N`, `-closed.N`, production).
+ - Establish whether the **latest shipped production build** is affected vs. only older un-updated installs β the single most important question for prioritization.
+
+3. **Locate the suspect code.** From the top app frames (ignore framework/SDK frames), use `Grep`/`Glob`/`Read` to find file:line. Note the KMP source set (commonMain vs androidMain) and owning module. If frames point into a library (ktor, maps, kable, MQTT client), say so β the fix may live in a sibling repo (e.g. MQTTastic-Client-KMP).
+
+4. **Form a root-cause hypothesis.** Tie the error + frames + device/OS/state breakdown together. Note correlations (specific OEM, Android version, foreground/background, reconnect storm, etc.).
+
+5. **Cross-check Crashlytics if relevant.** If this looks like a known Crashlytics issue, note it so the caller can dedupe across backends β but don't pull Crashlytics yourself (that's `crash-investigator`'s job).
+
+## What to return (and ONLY this)
+A compact report, no preamble:
+
+```
+RUM ERROR: <issue.id> β <exception class @ top app frame>
+STATUS: <NEW / REGRESSION / KNOWN / LIKELY-ALREADY-FIXED> + one-line why
+AFFECTED BUILDS: <versionCode(s)> -> <tag(s)> / <commit short shas> / <Play track>
+ LATEST PROD AFFECTED? <yes/no β build NNNN; this drives priority>
+VOLUME: <events / sessions over the window; trend up/flat/down>
+SUSPECT: <module>/<path:line> (<commonMain|androidMain>) [or: library frame -> <which repo>]
+ROOT CAUSE (hypothesis): <2-4 lines tying error + frames + device/state breakdown together>
+CORRELATIONS: <only if the breakdown shows one β OEM / OS / state>
+CRASHLYTICS OVERLAP: <likely-same-as <issue> / RUM-only / unknown>
+SUGGESTED FIX AREA: <where a fix would go; do NOT write it>
+NOTES: <related issues, cross-repo ownership, uncertainty>
+```
+
+Rules:
+- NEVER paste full stack traces or raw RUM event JSON. Quote at most the few frames that pin the location. (This is why `detailed_output:false` is mandatory.)
+- Be faithful about uncertainty: if you couldn't confirm the versionCodeβcommit mapping, say so rather than guessing.
+- If the data shows the latest prod build is clean, lead with that β it changes everything downstream.
+- Privacy: never surface user identifiers, locations, or key material from RUM payloads.
diff --git a/.claude/hooks/post-edit.sh b/.claude/hooks/post-edit.sh
index d4ef3533de..014480762d 100755
--- a/.claude/hooks/post-edit.sh
+++ b/.claude/hooks/post-edit.sh
@@ -66,6 +66,22 @@ $out"
*settings.gradle.kts)
emit_context "You edited settings.gradle.kts. If you added a NEW TOP-LEVEL module directory, add its '<root>/**' line to the 'android:' paths-filter in .github/workflows/pull-request.yml (case-sensitive) or the verify-check-changes-filter drift guard will fail the PR (bit us on #5735). New sub-modules under an already-listed root (core/**, feature/**, etc.) are already covered β no change needed."
;;
+
+ */src/commonMain/*.kt|*/src/commonTest/*.kt)
+ # KMP No-Framework-Bleed (AGENTS.md): common source sets compile to iOS/JS too,
+ # so java.*/android.* imports are illegal there. detekt's ForbiddenImport is
+ # empty AND can't scope to a source set, so nothing else catches this until the
+ # (slow, skippable) kmpSmokeCompile/iOS build. Cheap grep, blocks at edit time.
+ bleed=$(grep -nE '^[[:space:]]*import[[:space:]]+(java|android)\.' "$file_path" 2>/dev/null)
+ if [ -n "$bleed" ]; then
+ {
+ printf '%s\n' "KMP boundary violation β $file_path is a common source set but imports java.*/android.*:"
+ printf '%s\n' "$bleed"
+ printf '%s\n' "Use KMP equivalents (Okio for IO, kotlinx Mutex/atomicfu, kotlinx-datetime) or move the platform code to androidMain/jvmMain via expect/actual. (AGENTS.md No-Framework-Bleed; not caught until kmpSmokeCompile.)"
+ } >&2
+ exit 2
+ fi
+ ;;
esac
exit 0
diff --git a/.claude/skills/baseline/SKILL.md b/.claude/skills/baseline/SKILL.md
new file mode 100644
index 0000000000..2176354592
--- /dev/null
+++ b/.claude/skills/baseline/SKILL.md
@@ -0,0 +1,44 @@
+---
+name: baseline
+description: Run the mandatory pre-push baseline verification for Meshtastic-Android β bootstrap, then spotlessApply/spotlessCheck/detekt/assembleDebug/test/allTests (plus kmpSmokeCompile and sort-strings when relevant) via the gradle-runner subagent, restore the host-render screenshot diff, and report a single pass/fail. Use before every push. This is the check CI fails on when skipped.
+disable-model-invocation: true
+---
+
+# baseline
+
+The repo's verify-before-push gate, codified. CLAUDE.md/AGENTS.md mandate this before every push and CI has failed repeatedly when it was skipped. Run it, don't paraphrase it.
+
+## 1. Bootstrap (don't skip β agent workspaces often lack these)
+```bash
+[ -z "$ANDROID_HOME" ] && export ANDROID_HOME="$HOME/Library/Android/sdk"
+[ -f local.properties ] || cp secrets.defaults.properties local.properties
+```
+
+## 2. Decide the command from what changed
+```bash
+git diff --name-only HEAD && git diff --cached --name-only
+```
+- **Strings touched** (`core/resources/.../values/strings.xml`): prepend `python3 scripts/sort-strings.py` (the PostToolUse hook usually already did this; running it again is a no-op if so).
+- **A KMP module touched** (anything under `core/**`, `feature/**` with a `commonMain` source set): add `kmpSmokeCompile` to the gradle task list.
+- **New top-level module**: confirm its `<root>/**` line is in `.github/workflows/pull-request.yml` `android:` filter (else the drift guard fails the PR β #5735).
+
+## 3. Run it via the gradle-runner subagent
+Dispatch **gradle-runner** (keep the multi-thousand-line log out of context). The baseline is:
+```
+./gradlew spotlessApply spotlessCheck detekt assembleDebug test allTests
+```
+Add `kmpSmokeCompile` to that line if step 2 flagged a KMP module. Both `test` **and** `allTests` are required β `allTests` covers KMP modules (where bare `test` silently skips), `test` covers pure-Android/JVM modules.
+
+## 4. Verify the tree wasn't mutated, then clean the screenshot diff
+The gradle-runner subagent has Bash and has been observed reverting/editing files to force a green build. **After it returns, confirm the only changes are yours:**
+```bash
+git status --short
+```
+- If gradle-runner touched files you didn't, treat its PASS as suspect and re-run the failing task inline.
+- The full baseline regenerates tracked screenshots as host-render noise on this machine β drop them so they don't pollute the PR:
+```bash
+git checkout -- docs/assets/screenshots/
+```
+
+## 5. Report
+One line: `BASELINE PASS` (+ any inline re-runs you did) or `BASELINE FAIL` with the failing task/test names from gradle-runner. Do not push on FAIL.
Served by rngit 1.5.0 - Generated in 0.08s